#ifndef TIME_PRINTER_H
#define TIME_PRINTER_H

//时间打印器，便于观察每个步骤的时间开销
//使用方法是，先用init_time_printer()初始化一个计时器，返回一个该计时器的指针。初始化的时候需要指定一个名字，便于打印的时候区分。可以初始化多个计时器同时工作。
//然后每次想记录时间的时候，就用record_time_printer()记录一个时间戳
//记录结束或者记录中途想要打印时间的时候，可以用print_time_printer()打印出当前几个时间戳间的间隔时间
//当使用结束的时候，需要调用delete_time_printer()销毁时间打印器

#include <sys/time.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>

//时间戳以链表的形式存储，以下是链表的节点结构，我们不区分头节点和中间节点。
struct time_printer_node
{
    unsigned long time; //如果是头节点，则这个字段会被强行类型转换为struct time_printer_node*, 用于指向尾结点便于插入
    char* tag;
    short disable; //如果不为0，则表示时间记录功能关闭，不进行时间记录。便于调试，当不需要进行时间记录时也不用把所有打的时间戳删除掉。
    struct time_printer_node* next;
};

//获取一个空节点
struct time_printer_node* get_empty_node(){
    struct time_printer_node* node = NULL;
    node = (struct time_printer_node*)malloc(sizeof(struct time_printer_node));
    node->next = NULL;
    node->tag = NULL;
    node->time = 0;
    node->disable = 0;
    return node;
}

//将字符串存储到节点中，需要新开辟一个内存空间并拷贝
void set_node_name(struct time_printer_node* node, char* name){
    if(node == NULL || name == NULL){
        printf("[set_node_name]empty pointer!\n");
        return;
    }

    if(node->tag != NULL) //如果节点名字段已经存有字符串，则先释放该内存空间
        free(node->tag);

    int len_name = strlen(name);
    char* name_copy = (char*)malloc(len_name + 1);
    memcpy(name_copy, name, len_name + 1);

    node->tag = name_copy;
    return;
}

//用于获取当前时间，单独抽象出来成一个函数便于移植到不同平台。返回的时间以us为单位
unsigned long get_time(){
    struct timeval tmp;
    gettimeofday(&tmp, 0);
    return tmp.tv_sec*1e6 + tmp.tv_usec;
}

//计算时间差, 返回值以us为单位
unsigned long time_printer_elapsed(unsigned long start, unsigned long end){
    return end - start;
}

void* init_time_printer(char* printer_name){
    struct time_printer_node* head = get_empty_node();
    set_node_name(head, printer_name);
    head->time = (unsigned long)head; //头结点的time代表了尾结点，便于插入。因此初始化头节点的时候让尾结点指向自己。

    return (void*)head;
}

void record_time_printer(void* head, char* point_name){
    if(head == NULL || point_name == NULL){
        printf("[record_time_printer]empty pointer!\n");
        return;
    }

    struct time_printer_node* root = (struct time_printer_node*)head;
    if(root->disable){
        return;
    }

    //初始化新节点
    struct time_printer_node* node = get_empty_node();
    set_node_name(node, point_name);
    node->time = get_time();

    //获取到尾结点
    struct time_printer_node* tail = (struct time_printer_node*)(((struct time_printer_node*)head)->time);
    tail->next = node;
    ((struct time_printer_node*)head)->time = (unsigned long)node;

    return;
}

void print_time_printer(void* head){
    if(head == NULL){
        printf("[print_time_printer]empty pointer!\n");
        return;
    }

    struct time_printer_node* iter = (struct time_printer_node*)head;
    struct time_printer_node* tail = (struct time_printer_node*)(iter->time);

    if(iter->disable){
        return;
    }

    //打印总时间
    //首先排除节点数量不够的情况，至少要记录了两个时间节点才能打印时间间隔
    if(iter == tail || iter->next == tail){
        printf("Only 0 or 1 nodes are recorded, can't print interval\n");
        return;
    }

    printf("===================Print time printer====================\n");
    printf("Total elapsed time of %s is: %lu us\n", iter->tag, time_printer_elapsed(iter->next->time, tail->time));
    printf("Time interval details are:\n");
    iter = iter->next; //指向第一个真正的时间节点

    while (iter->next != NULL) //因为我们是打印时间间隔，所以要保证后一个节点存在，否则就无法计算和后一个节点的时间间隔了
    {
        printf("[-from %s to %s, interval:%lu us\n",iter->tag, iter->next->tag, time_printer_elapsed(iter->time, iter->next->time));
        iter = iter->next;
    }

    return;
}

void delete_time_printer(void* head){
    if(head == NULL){
        printf("[delete_time_printer]empty pointer!\n");
        return;
    }

    struct time_printer_node* tmp_iter; //用来保存前一个节点用来删除
    struct time_printer_node* iter = (struct time_printer_node*)head;
    while(iter != NULL){
        if(iter->tag != NULL)
            free(iter->tag); //释放之前分配的用来记录节点名的内存

        tmp_iter = iter;
        iter = iter->next;
        free(tmp_iter); //释放节点本身
    }

    return;
}

//record the last time, it will record the finish time, print time, and delete printer
void record_last_time(void* head){
    struct time_printer_node* root = (struct time_printer_node*)head;
    if(root == NULL){
        printf("[record_last_time]Error, head is null\n");
        return;
    }
    if(!root->disable){
        record_time_printer(head, "finish");
        print_time_printer(head);
    }
    delete_time_printer(head);
}

void* init_record_time_printer(char* printer_name){
    void* head = init_time_printer(printer_name);
    record_time_printer(head, "init");
    return head;
}

void enable_time_printer(void* head){
    struct time_printer_node* root = (struct time_printer_node*)head;
    root->disable = 0;
}

void disable_time_printer(void* head){
    struct time_printer_node* root = (struct time_printer_node*)head;
    root->disable = 1;
}

#endif